--- name: html-to-pptx description: Convert a single-file HTML slide deck into a pixel-perfect PPTX by rendering it in headless Chrome, taking a full-page screenshot, slicing it into per-slide images with Pillow, and packing them into a PowerPoint file. Use when the user has an HTML presentation and wants a .pptx export. --- # HTML to PPTX Skill Convert any single-file HTML slide deck into a `.pptx` file that looks exactly like the browser render — fonts, glows, gradients, animations frozen at their initial state. ## How It Works 1. **Headless Chrome** (via Puppeteer) renders the HTML at 1920×1080 and takes a single `fullPage: true` screenshot → one tall PNG 2. **Pillow** slices that PNG into N equal strips (one per slide) 3. **python-pptx** packs each strip as a full-bleed image on its own PPTX slide This avoids all scroll/clip coordinate bugs — each slide is a guaranteed unique pixel crop. --- ## Requirements Check Before running, verify these are available: - `node` + `puppeteer` npm package (installed locally or globally) - `python` with `pillow` and `python-pptx` packages - Chrome or Edge executable on the system --- ## Step-by-Step Instructions ### 1. Ask the user for inputs You need: - **HTML file path** — the source presentation - **Number of slides** — how many slides the deck has - **Output PPTX filename** — where to save (default: same folder as HTML, same name with `.pptx`) - **Slide aspect ratio** — default 16:9 (1920×1080). Ask only if non-standard. - **Chrome path** — auto-detect from common locations, only ask if not found Auto-detect Chrome from these paths (check in order): - `C:/Program Files/Google/Chrome/Application/chrome.exe` - `C:/Program Files (x86)/Google/Chrome/Application/chrome.exe` - `C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe` - `/usr/bin/google-chrome` - `/usr/bin/chromium-browser` ### 2. Install puppeteer if needed ```bash # Check node -e "require('puppeteer')" 2>&1 # Install locally in the HTML file's directory if missing npm install puppeteer --save-dev ``` ### 3. Write the screenshot script Write `_screenshot.mjs` to the same folder as the HTML: ```js import puppeteer from 'puppeteer'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const HTML_PATH = path.join(__dirname, ''); const W = 1920; const H = 1080; const browser = await puppeteer.launch({ executablePath: '', headless: true, args: ['--no-sandbox', `--window-size=${W},${H}`], }); const page = await browser.newPage(); await page.setViewport({ width: W, height: H, deviceScaleFactor: 1 }); await page.goto(`file:///${HTML_PATH.replace(/\\/g, '/')}`, { waitUntil: 'networkidle0', timeout: 30000, }); // Wait for fonts and transitions to settle await new Promise(r => setTimeout(r, 2500)); // Freeze animations, hide fixed UI chrome await page.addStyleTag({ content: ` * { animation: none !important; transition: none !important; } body::after { display: none !important; } #nav, #progress, nav, header[data-fixed] { display: none !important; } ` }); await page.screenshot({ path: path.join(__dirname, '_fullpage.png'), fullPage: true, }); console.log('Done.'); await browser.close(); ``` ### 4. Write the slice + pack script Write `_pack_pptx.py` to the same folder: ```python from PIL import Image from pptx import Presentation from pptx.util import Inches import os FOLDER = r'' FULLPAGE = os.path.join(FOLDER, '_fullpage.png') OUT = os.path.join(FOLDER, '') SLIDES = img = Image.open(FULLPAGE) W, H = img.size slide_h = H // SLIDES print(f"Full image: {W}x{H}, {SLIDES} slides, {slide_h}px each") prs = Presentation() prs.slide_width = Inches(20) # 1920px at 96dpi prs.slide_height = Inches(11.25) # 1080px at 96dpi blank = prs.slide_layouts[6] for i in range(SLIDES): top = i * slide_h crop = img.crop((0, top, W, top + slide_h)) tmp = os.path.join(FOLDER, f'_slide_{i+1:02d}.png') crop.save(tmp) slide = prs.slides.add_slide(blank) slide.shapes.add_picture(tmp, left=0, top=0, width=prs.slide_width, height=prs.slide_height) print(f" Slide {i+1:02d} rows {top}-{top+slide_h}") prs.save(OUT) print(f"Saved: {OUT}") ``` ### 5. Run both scripts ```bash node _screenshot.mjs python _pack_pptx.py ``` ### 6. Clean up temp files After confirming the PPTX looks correct, delete: - `_fullpage.png` - `_screenshot.mjs` - `_pack_pptx.py` - `_slide_01.png` … `_slide_NN.png` --- ## Common Issues | Problem | Cause | Fix | |---|---|---| | All slides identical | Using `clip:{y:0}` on scrolled viewport | Always use `fullPage:true` + slice — never scroll | | PermissionError on save | PPTX open in PowerPoint | Close the file first, or save to a new filename | | Fonts wrong in PPTX | Custom web fonts not installed locally | This approach embeds pixel images so fonts are always correct | | Slide count wrong | `H // SLIDES` rounding | Check `H % SLIDES == 0`; if not, slides have `min-height` inconsistency in HTML | | Chrome not found | Wrong exe path | Check Edge as fallback |